package gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import javax.swing.BorderFactory;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;

import main.DatosProblema;
import main.Util;
import simplex.ParserEcuacion;
import simplex.Racional;
import simplex.Simplex;

/**
 * Definicion del panel principal del programa.
 * 
 * @author age, joaquinmonleon
 *
 */
public class PrincipalGUI extends JFrame {
	
	private static final long serialVersionUID = -6188652110201723083L;
	
	private AyudaPanel ap;	
	private NuevoProblemaPanel pNuevoProblema;
	
	private KeyAdapter kaDecimalField;
	
	private JPanel pMenu;
	private JPanel pCentral;
	private JPanel pResultados;

	private JTabbedPane tpDatosProblema;
	private JPanel pDatosProblemaTabla;

	private DefaultTableModel dtmDatosProblemaTabla;
	private	String [] colsHeader; 
	private	String [] rowsHeader; 
	private	String [][] dataDatosProblemaTabla; 
	private JTable tDatosProblemaTabla;
	private JScrollPane spDatosProblemaTabla;

	private JPanel pDatosProblemaTexto;
	private JScrollPane spDatosProblemaTexto;
	private JTextArea taDatosProblemaTexto;

	private JButton bNuevoProblema;
	private JButton bAyuda;
	private JButton bBorrarResultados;
	private JButton bResolver;
	
	private JScrollPane spResultados;
	private JTextArea taResultados;
	
	public PrincipalGUI () {
		inicializarPanel ();
	}
	
	private void inicializarPanel () {		
		
		// Detalles globales del panel
		this.setTitle("Solvex");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		kaDecimalField = new KeyAdapter () {
			public void keyTyped(KeyEvent e) {
				// Verificar si la tecla pulsada no es un digito y punto
				if((e.getKeyChar() < '0' || e.getKeyChar() > '9') 
						&& e.getKeyChar() != '.' && e.getKeyChar() != '+' && e.getKeyChar() != '-') {
					e.consume();
				}
			}
		};
		
		// Inicializar componentes
		pNuevoProblema = new NuevoProblemaPanel (
				PrincipalGUI.this, "Nuevo problema", true);
		pNuevoProblema.setLocationRelativeTo (PrincipalGUI.this);
		ap = new AyudaPanel();
		
		tpDatosProblema = new JTabbedPane();

		pMenu = new JPanel ();	
		pMenu.setBorder(
				BorderFactory.createTitledBorder(" Menú "));
		pCentral = new JPanel ();
		pCentral.setLayout(Layout.getLayout());
		pResultados = new JPanel ();
		pResultados.setBorder(
				BorderFactory.createTitledBorder(" Resultados "));
		
		bNuevoProblema = new JButton ("Nuevo problema tabla");
		bNuevoProblema.addActionListener (new Gestor());
		bAyuda = new JButton ("Ayuda modo texto");
		bAyuda.addActionListener (new Gestor());
		bBorrarResultados = new JButton ("Borrar resultados");
		bBorrarResultados.addActionListener (new Gestor());
		bResolver = new JButton ("Resolver");
		bResolver.addActionListener (new Gestor());

		tDatosProblemaTabla = new JTable() {
			private static final long serialVersionUID = -7306249298011631496L;
			@Override
			public boolean isCellEditable(int row, int column) {
				// Evitar la edicion de la celda de Max Min
			    if(row == 0 && column == this.getColumnCount()-1) return false;  
			    return true;				
			}
		};
		tDatosProblemaTabla.setFillsViewportHeight(true);
		tDatosProblemaTabla.addKeyListener(kaDecimalField);
		tDatosProblemaTabla.setCellSelectionEnabled(false);
		tDatosProblemaTabla.setInputVerifier(new InputVerifier() {
			@Override
			public boolean verify(JComponent input) {
				// Esto impide que se edite completamente la celda
				return false;
			}
		});
		spDatosProblemaTabla = new JScrollPane(tDatosProblemaTabla);
		spDatosProblemaTabla.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
		pDatosProblemaTabla = new JPanel ();
		//pDatosProblema.setBorder(BorderFactory.createTitledBorder(" Datos problema "));
		pDatosProblemaTabla.add(spDatosProblemaTabla);
		pDatosProblemaTabla.setLayout(new BorderLayout());
		pDatosProblemaTabla.add(tDatosProblemaTabla.getTableHeader(), BorderLayout.PAGE_START);
		pDatosProblemaTabla.add(spDatosProblemaTabla, BorderLayout.CENTER);
		tpDatosProblema.addTab("Datos del problema en tabla", pDatosProblemaTabla);

		taDatosProblemaTexto = new JTextArea(1, 1);
		taDatosProblemaTexto.setLineWrap(true);
		spDatosProblemaTexto = new JScrollPane(taDatosProblemaTexto);
		spDatosProblemaTexto.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
		pDatosProblemaTexto = new JPanel ();
		pDatosProblemaTexto.add(spDatosProblemaTexto);
		pDatosProblemaTexto.setLayout(new BorderLayout());
		pDatosProblemaTexto.add(spDatosProblemaTexto, BorderLayout.CENTER);
		tpDatosProblema.addTab("Datos del problema en texto", pDatosProblemaTexto);
		
		taResultados = new JTextArea(9, 2);
		taResultados.setLineWrap(true);
		taResultados.setEditable(false);
		spResultados = new JScrollPane(taResultados);
		spResultados.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
		pResultados.add(spResultados);
		pResultados.setLayout(new BorderLayout());
		pResultados.add(spResultados, BorderLayout.CENTER);
		
		// Añadir al menu
		pMenu.add (bNuevoProblema);
		pMenu.add (bAyuda);
		pMenu.add (bBorrarResultados);
		pMenu.add (bResolver);
		
		// Añadir al panel central
		Layout.addItem(0, 0, pCentral, tpDatosProblema);
		Layout.addItem(1, 0, pCentral, pResultados);
		
		// Añadir al panel principal
		getContentPane().add (pMenu, BorderLayout.NORTH);
		getContentPane().add (pCentral, BorderLayout.CENTER);
		
		// Problema por defecto
		inicializarProblema ();
		
		redirectSystemStreamsTextArea();
	}
	
	private Double[][] obtenerMatrizDatos () {
		Double[][] matriz = new Double[DatosProblema.Restricciones+1][DatosProblema.Variables+1];
		
		for (int i=0; i<DatosProblema.Restricciones+1; i++) {
			for (int j=0; j<DatosProblema.Variables+1; j++) {
				if (i == 0 && j == DatosProblema.Variables) {
					matriz[i][j] = 0.0;
				} else {
					String value = (String) dtmDatosProblemaTabla.getValueAt(i, j);
					if (value == null || value.isEmpty()) {
						matriz[i][j] = 0.0;
					} else if (!Util.isDouble(value)) {
						// Caso de error - sale devolviendo null
						JOptionPane.showMessageDialog(null, "La matriz de datos solo debe contener datos numericos.", "Error", JOptionPane.ERROR_MESSAGE);
						return null;
					} else {
						matriz[i][j] = Double.parseDouble((String)dtmDatosProblemaTabla.getValueAt(i, j));
					}
				}
			}
		}
		
		return matriz;
	}
	
	private void inicializarProblema () {
		// Inicializar contenido y nombres de columna en tabla
		colsHeader = new String[DatosProblema.Variables+1];
		rowsHeader = new String[DatosProblema.Restricciones+1];
		dataDatosProblemaTabla = new String[DatosProblema.Restricciones+1][DatosProblema.Variables+1];
		
		// Asignar nombres de las variables y termino independiente en cabecera
		for (int i=0; i<DatosProblema.Variables; i++) {
			colsHeader[i] = "x" + (i+1);
		}
		colsHeader[DatosProblema.Variables] = "Ind";
		
		// Asignar nombres del objetivo y restricciones en la cabecera de filas
		rowsHeader[0] = "Obj";
		for (int i=1; i<DatosProblema.Restricciones+1; i++) {
			rowsHeader[i] = "r" + (i);
		}
		
		// Preparar nuevo modelo interno de tabla
		dtmDatosProblemaTabla = new DefaultTableModel(dataDatosProblemaTabla, colsHeader);
		tDatosProblemaTabla.setModel(dtmDatosProblemaTabla);
		
	    JList lRowHeaders = new JList(rowsHeader);
	    lRowHeaders.setFixedCellWidth(50);
	    lRowHeaders.setFixedCellHeight(tDatosProblemaTabla.getRowHeight());
	    lRowHeaders.setCellRenderer(new RowHeaderRenderer(tDatosProblemaTabla));

	    spDatosProblemaTabla.setRowHeaderView(lRowHeaders);
	    
	    tDatosProblemaTabla.setValueAt(DatosProblema.OptimoText(), 0, DatosProblema.Variables);
	    
	    // Lo ultimo ha hacer al cambiar el contenido del panel
		this.pack();
	}
	
	private class Gestor implements ActionListener {

		public Gestor() {}

	    public void actionPerformed(ActionEvent e) {
			if (e.getSource() == bNuevoProblema) {
				pNuevoProblema.cargarPanel();
				if (pNuevoProblema.AceptarPulsado) {
					inicializarProblema ();
				}
			} else if (e.getSource() == bAyuda) {
				ap.setVisible(true);
			} else if (e.getSource() == bBorrarResultados) {
				taResultados.setText("");
			} else if (e.getSource() == bResolver) {
				if (tpDatosProblema.getSelectedComponent().equals(pDatosProblemaTabla)) {
					if (tDatosProblemaTabla.isEditing())
						tDatosProblemaTabla.getCellEditor().stopCellEditing();
					
					Double [][] matrizCompleta = obtenerMatrizDatos ();
					if (matrizCompleta != null) {
						/*
						//temp
						for (int i=0; i<matrizCompleta.length; i++) {
							for (int j=0; j<matrizCompleta[i].length; j++) {
								System.out.println(matrizCompleta[i][j]+" ");							
							}
							System.out.println();
						}
						*/
						
						// Desglosar la matriz completa en las partes que necesita Simplex
						Racional [] objetivo = new Racional [DatosProblema.Variables];					
						Racional [] termIndep = new Racional [DatosProblema.Restricciones];
						Racional [][] datos = new Racional [DatosProblema.Restricciones][DatosProblema.Variables];
						for (int i=0; i<matrizCompleta.length; i++) {
							for (int j=0; j<matrizCompleta[i].length; j++) {
								// Relleno de objetivo
								if (i == 0 && j >= 0 && j < matrizCompleta[i].length-1) {
									objetivo[j] = new Racional(matrizCompleta[i][j].intValue(), 1);
								}
								
								// Relleno de termino indep
								if (j == matrizCompleta[i].length-1 && i >= 1 && i < matrizCompleta.length) {
									termIndep[i-1] = new Racional(matrizCompleta[i][j].intValue(), 1);
								}
								
								// Relleno de datos
								if (i >= 1 && j < matrizCompleta[i].length-1) {
									datos[i-1][j] = new Racional(matrizCompleta[i][j].intValue(), 1);
								}
							}
						}
						
						// Evaluar optimalidad
						int signo = 
							(DatosProblema.Optimo == DatosProblema.TOptimo.Max
							|| DatosProblema.Optimo == DatosProblema.TOptimo.Min
							? 1 : -1);
						boolean maximizar = 
							(DatosProblema.Optimo == DatosProblema.TOptimo.Max
							|| DatosProblema.Optimo == DatosProblema.TOptimo.menosMax
							? true : false);
						
						Simplex simplex = new Simplex(signo, maximizar, "z", objetivo, termIndep, DatosProblema.Variables, DatosProblema.Restricciones, datos);
						simplex.resuelve();
					}
				} else if (tpDatosProblema.getSelectedComponent().equals(pDatosProblemaTexto)) {
					ParserEcuacion pe = new ParserEcuacion();
					Simplex simplex = pe.parse(taDatosProblemaTexto.getText());
					
					if (simplex != null) {
						simplex.resuelve();
					}
				}
			}
   		 }
	}
	private void updateTextArea(final String text) {  
		SwingUtilities.invokeLater(new Runnable() {  
			public void run() {  
				taResultados.append(text);  
			}  
		});  
	}
	private void redirectSystemStreamsTextArea() {  
		OutputStream out = new OutputStream() {  
			@Override  
			public void write(int b) throws IOException {  
				updateTextArea(String.valueOf((char) b));  
			}  

			@Override  
			public void write(byte[] b, int off, int len) throws IOException {  
				updateTextArea(new String(b, off, len));  
			}  
        
			@Override  
			public void write(byte[] b) throws IOException {  
				write(b, 0, b.length);  
			}  
		};  
		
		System.setOut(new PrintStream(out, true));  
		System.setErr(new PrintStream(out, true));  
	}
}

// --- Clases privadas utiles ---

// Componente Row Header
class RowHeaderRenderer extends JLabel implements ListCellRenderer {
	private static final long serialVersionUID = 6582071187533526361L;

	RowHeaderRenderer(JTable table) {
		JTableHeader header = table.getTableHeader();
		setOpaque(true);
		setBorder(UIManager.getBorder("TableHeader.cellBorder"));
		setHorizontalAlignment(CENTER);
		setForeground(header.getForeground());
		setBackground(header.getBackground());
		setFont(header.getFont());
	}

	public Component getListCellRendererComponent(JList list, Object value,
			int index, boolean isSelected, boolean cellHasFocus) {
		setText((value == null) ? "" : value.toString());
		return this;
	}
}
